package cn.okcode.api.framework.js;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 中间指令解释器
 *
 * @author pyk13
 */
public class ILInterpreter {

    public final StringBuilder logger = new StringBuilder();

    /**
     * 存放指令数组
     */
    private final String[] opCodes;

    /**
     * 存放执行结果，如果有的话
     */
    private Object result;

    /**
     * 方法缓存
     */
    private final static Map<String, Method> methodCache = new HashMap<>();

    /**
     * 构造函数缓存
     */
    private final static Map<String, Constructor<?>> constructorCache = new HashMap<>();

    /**
     * 参数表
     */
    private final Object[] locals = new Object[1000];
    /**
     * 参数类型表，与参数一一对应
     **/
    private final Object[] localTypes = new Object[1000];

    /**
     * 执行栈
     */
    private final Stack<Object> evaluateStack = new Stack<>();

    public ILInterpreter(String[] opCodes) {
        this.opCodes = opCodes;
    }

    /**
     * 执行方法入口
     *
     * @return 执行结果
     */
    public Object execute() {
        for (int i = 0; i < opCodes.length; i++) {
            logger.append(i).append(":");
            logger.append(opCodes[i]);
            logger.append(System.lineSeparator());
        }

        int index = 0;
        try {
            for (; index < opCodes.length; index++) {
                String opCode = opCodes[index];
                String[] arr = opCode.split(" ");
                if (arr.length == 0) {
                    throw new RuntimeException("操作码不能为空！");
                }
                String code = arr[0];
                Object[] args = null;
                if (arr.length > 1) {
                    args = Arrays.copyOfRange(arr, 1, arr.length);
                }
                execute(code, args);
            }
        } catch (Exception e) {
            logger.append("第").append(index).append("行指令").append(opCodes[index]).append("执行出错");
            logger.append(System.lineSeparator());
            logger.append(e.getLocalizedMessage());
            throw new RuntimeException(logger.toString());
        }
        return result;
    }

    /**
     * 执行单条指令
     * <p>
     * 指令格式：指令码 参数1,参数2,...
     *
     * @param opCode 指令码
     * @param args   参数
     */
    private void execute(String opCode, Object[] args) {
        switch (opCode) {
            case OpCodes.loadclass:
                loadclass();
                break;
            case OpCodes.stloc:
                stloc(parseLocalIndex(args));
                break;
            case OpCodes.ldloc:
                ldloc(parseLocalIndex(args));
                break;
            case OpCodes.stloctype:
                stloctype(parseLocalIndex(args));
                break;
            case OpCodes.ldloctype:
                ldloctype(parseLocalIndex(args));
                break;
            case OpCodes.ldc:
                ldc(args[0].toString());
                break;
            case OpCodes.ldf:
                ldf(parseFloat(args));
                break;
            case OpCodes.ldi:
                ldi(parseInteger(args));
                break;
            case OpCodes.call:
                call();
                break;
            case OpCodes.callA:
                callA();
                break;
            case OpCodes.callvirtual:
                callvirtual();
                break;
            case OpCodes.callvirtualA:
                callvirtualA();
                break;
            case OpCodes.ctor:
                ctor();
            case OpCodes.ctorA:
                ctorA();
                break;
            case OpCodes.ret:
                ret();
                break;
        }
    }

    /**
     * 解析参数为变量索引
     *
     * @param args 参数集合
     * @return 变量索引
     */
    private int parseLocalIndex(Object[] args) {
        if (args.length == 0) {
            throw new RuntimeException("解析变量索引出错,参数长度不够");
        }
        Object objIndex = args[0];
        return Integer.parseInt(objIndex.toString());
    }

    /**
     * 从参数中获取浮点数
     *
     * @param args 参数集合
     * @return 获取到的浮点数
     */
    private float parseFloat(Object[] args) {
        if (args.length == 0) {
            throw new RuntimeException("解析浮点变量出错,参数长度不够");
        }
        Object objIndex = args[0];
        return Float.parseFloat(objIndex.toString());
    }

    /**
     * 从参数中获整数
     *
     * @param args 参数集合
     * @return 获取到的整数
     */
    private int parseInteger(Object[] args) {
        if (args.length == 0) {
            throw new RuntimeException("解析整数变量出错,参数长度不够");
        }
        Object objIndex = args[0];
        return Integer.parseInt(objIndex.toString());
    }

    /**
     * 从执行栈上弹出一个字符串，并作为类型名来加载
     * 将加载的类型对象放回执行栈顶
     */
    private void loadclass() {
        String className = evaluateStack.pop().toString();
        Class<?> clazz;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        evaluateStack.push(clazz);
    }

    /**
     * 将栈顶的值弹出并放到变量表指定位置
     *
     * @param index 将要存放的位置
     */
    private void stloc(int index) {
        locals[index] = evaluateStack.pop();
    }

    /**
     * 将栈顶的值存到索变量类型表的指定位置
     * <p>
     * 实际上，这里从栈顶弹出的值应该是一个索引，索引是指向变量表的一个类型对象
     * <p>
     * 在本解释器中，所有临时对象都是存在变量表，变量类型表实际只是存在的类型在变量表上的索引而已
     *
     * @param index
     */
    private void stloctype(int index) {
        localTypes[index] = Integer.parseInt(evaluateStack.pop().toString());
    }

    /**
     * 加载变量表中指定索引的值到栈顶
     *
     * @param index 变量表索引
     */
    private void ldloc(int index) {
        evaluateStack.push(locals[index]);
    }

    /**
     * 加载类型到栈顶
     *
     * @param index 类型所在变量表的索引
     */
    private void ldloctype(int index) {
        int localTypeIndex = Integer.parseInt(locals[index].toString());
        evaluateStack.push(localTypes[localTypeIndex]);
    }

    /**
     * 加载常量字符串到栈顶
     *
     * @param value 字符串值
     */
    private void ldc(String value) {
        evaluateStack.push(value);
    }

    /**
     * 加载常量整数到栈顶
     *
     * @param value 整型数值
     */
    private void ldi(int value) {
        evaluateStack.push(value);
    }

    /**
     * 加载常量浮点数值到栈顶
     *
     * @param value 浮点数值
     */
    private void ldf(float value) {
        evaluateStack.push(value);
    }

    /**
     * 虚拟调用，即实例化调用
     * 该方法会根据传入的参数类型，准确的调用
     */
    private void callvirtual() {
        String methodName = evaluateStack.pop().toString();
        Object ownerObject = evaluateStack.pop();
        Class<?> ownerClass = ownerObject.getClass();
        doCall(ownerClass, methodName, ownerObject);
    }

    /**
     * 实例化调用
     * 该方法会尝试根据参数匹配最佳的调用方法
     */
    private void callvirtualA() {
        String methodName = evaluateStack.pop().toString();
        Object ownerObject = evaluateStack.pop();
        Class<?> ownerClass = ownerObject.getClass();
        doCallA(ownerClass, methodName, ownerObject);
    }

    /**
     * 静态调用
     * 该方法会根据传入的参数类型，准确的调用
     */
    private void call() {
        String methodName = evaluateStack.pop().toString();
        Class<?> ownerClass = (Class<?>) evaluateStack.pop();
        doCall(ownerClass, methodName, ownerClass);
    }

    /**
     * 静态调用
     * 该方法会尝试根据参数匹配最佳的调用方法
     */
    private void callA() {
        String methodName = evaluateStack.pop().toString();
        Class<?> ownerClass = (Class<?>) evaluateStack.pop();
        doCallA(ownerClass, methodName, ownerClass);
    }

    private void doCall(Class<?> ownerClass, String methodName, Object owner) {
        List<Object> parameterValues = new ArrayList<>(evaluateStack.size());
        List<Class<?>> parameterTypes = new ArrayList<>(evaluateStack.size());
        List<String> parameterTypeNames = new ArrayList<>();
        while (!evaluateStack.empty()) {
            Class<?> parameterType = (Class<?>) evaluateStack.pop();
            Object parameterValue = castValue(evaluateStack.pop(), parameterType);
            parameterTypes.add(parameterType);
            parameterValues.add(parameterValue);
            parameterTypeNames.add(parameterType.getName());
        }
        Method callee;
        try {
            callee = ownerClass.getMethod(methodName, parameterTypes.toArray(new Class<?>[0]));
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("方法" + ownerClass.getName() + "." + methodName + "(" + join(parameterTypeNames, ",") + ")不存在", e);
        }
        try {
            Object result = callee.invoke(owner, parameterValues.toArray());
            if (!callee.getReturnType().equals(Void.TYPE)) {
                evaluateStack.push(result);
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("非法访问" + e.getLocalizedMessage(), e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("方法[" + callee.getName() + "]执行出错" + e.getCause().getLocalizedMessage(), e);
        }
    }

    private void doCallA(Class<?> ownerClass, String methodName, Object owner) {
        List<Object> parameterValues = new ArrayList<>(evaluateStack.size());
        while (!evaluateStack.empty()) {
            Object parameterValue = evaluateStack.pop();
            parameterValues.add(parameterValue);
        }

        //尝试匹配方法
        Method callee = matchMethodWithCache(methodName, ownerClass, parameterValues);

        if (callee == null) {
            throw new RuntimeException("无法匹配到签名为`" + methodName + "(" + join(parameterValues, ",") + ")" + "的方法");
        }

        try {
            Object result = callee.invoke(owner, parameterValues.toArray());
            if (!callee.getReturnType().equals(Void.TYPE)) {
                evaluateStack.push(result);
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("非法访问" + e.getLocalizedMessage(), e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("方法[" + callee.getName() + "]执行出错" + e.getCause().getLocalizedMessage(), e);
        }
    }

    /**
     * 执行给定参数类型的构造函数
     */
    private void ctor() {
        //从栈顶拿到类型
        Class<?> clazz = (Class<?>) evaluateStack.pop();
        //从栈顶获取参数，并根据参数类型进行转换
        List<Object> parameterValues = new ArrayList<>();
        List<Class<?>> parameterTypes = new ArrayList<>();
        List<String> parameterTypeNames = new ArrayList<>();
        while (!evaluateStack.empty()) {
            Class<?> parameterType = (Class<?>) evaluateStack.pop();
            Object parameterValue = castValue(evaluateStack.pop(), parameterType);

            parameterTypes.add(parameterType);
            parameterValues.add(parameterValue);
            parameterTypeNames.add(parameterType.getName());
        }
        Constructor<?> constructor;
        try {
            constructor = clazz.getConstructor(parameterTypes.toArray(new Class<?>[0]));
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("构造函数" + clazz.getName() + "(" + join(parameterTypeNames, ",") + ")不存在", e);
        }
        try {
            Object object = constructor.newInstance(parameterValues.toArray());
            evaluateStack.push(object);
        } catch (InstantiationException e) {
            throw new RuntimeException("指令异常" + e.getLocalizedMessage(), e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("非法访问" + e.getLocalizedMessage(), e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("类[" + clazz.getName() + "]构造方法执行异常", e);
        }
    }

    /**
     * 执行自动推导的构造函数
     */
    private void ctorA() {
        //从栈顶拿到类型
        Class<?> clazz = (Class<?>) evaluateStack.pop();

        //从栈顶获取参数
        List<Object> parameterValues = new ArrayList<>();
        while (!evaluateStack.empty()) {
            Object parameterValue = evaluateStack.pop();
            parameterValues.add(parameterValue);
        }

        //根据参数的实际类型进行匹配构造方法
        Constructor<?> constructor = matchConstructorWithCache(clazz, parameterValues);

        //执行构造方法调用
        try {
            Object object = constructor.newInstance(parameterValues.toArray());
            evaluateStack.push(object);
        } catch (InstantiationException e) {
            throw new RuntimeException("指令异常" + e.getLocalizedMessage(), e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("非法访问" + e.getLocalizedMessage(), e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("类[" + clazz.getName() + "]构造方法执行异常", e);
        }
    }

    /**
     * 返回值，即从栈顶弹出值到存放结果的对象上
     */
    private void ret() {
        result = evaluateStack.pop();
    }

    /**
     * 根据实参匹配方法，并缓存
     *
     * @param methodName      方法名
     * @param clazz           方法所在类
     * @param parameterValues 方法实参
     * @return
     */
    private Method matchMethodWithCache(String methodName, Class<?> clazz, List<Object> parameterValues) {
        List<String> parameterTypeNames = formatParameterTypeNames(parameterValues);
        Method method = null;
        String matchKey = null;
        if (parameterTypeNames.size() == parameterValues.size()) {
            matchKey = String.format("%s.%s(%s)", clazz.getName(), methodName, join(parameterTypeNames, ","));
            method = methodCache.get(matchKey);
        }
        if (method == null) {
            method = matchMethod(methodName, clazz, parameterValues);
            if (matchKey != null) {
                methodCache.put(matchKey, method);
            }
        }
        return method;
    }

    /**
     * 具体的根据实参匹配方法的算法
     *
     * @param methodName      方法名
     * @param clazz           方法所在类型
     * @param parameterValues 实参
     * @return 匹配到的方法，入股匹配不到会返回null
     */
    private Method matchMethod(String methodName, Class<?> clazz, List<Object> parameterValues) {
        System.out.println("开始匹配方法:" + methodName);
        Method[] methods = clazz.getMethods();

        int[] minLevels = new int[parameterValues.size()];
        Arrays.fill(minLevels, Integer.MAX_VALUE);

        Map<Method, int[]> methodLevels = new HashMap<>();
        for (Method method : methods) {
            if (!method.getName().equals(methodName)) {
                continue;
            }
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != parameterValues.size()) {
                continue;
            }
            System.out.println("开始计算签名优先级");
            int[] levels = calcIntimacy(parameterValues, parameterTypes, minLevels);
            System.out.println("完成计算签名优先级");
            methodLevels.put(method, levels);
        }
        for (int i = 0; i < minLevels.length; i++) {
            int minLevel = minLevels[i];
            if (minLevel == Integer.MAX_VALUE) {
                throw new RuntimeException("实参参数(" + parameterValues.get(i) + ")不能和任何形参匹配");
            }
        }
        return findPriorityMethod(methodLevels, minLevels);
    }

    /**
     * 具体的根据实参匹配构造方法的算法
     *
     * @param clazz           方法所在类型
     * @param parameterValues 实参
     * @return 匹配到的构造方法，入股匹配不到会返回null
     */
    private Constructor<?> matchConstructorWithCache(Class<?> clazz, List<Object> parameterValues) {
        List<String> parameterTypeNames = formatParameterTypeNames(parameterValues);
        Constructor<?> constructor = null;
        String matchKey = null;
        if (parameterTypeNames.size() == parameterValues.size()) {
            matchKey = String.format("%s(%s)", clazz.getName(), join(parameterTypeNames, ","));
            constructor = constructorCache.get(matchKey);
        }
        if (constructor == null) {
            constructor = matchConstructor(clazz, parameterValues);
            if (matchKey != null) {
                constructorCache.put(matchKey, constructor);
            }
        }
        return constructor;
    }

    /**
     * 将参数集合转换为参数类型集合
     *
     * @param parameterValues 参数集合
     * @return 参数类型集合
     */
    private List<String> formatParameterTypeNames(List<Object> parameterValues) {
        List<String> parameterTypeNames = new ArrayList<>(parameterValues.size());
        for (Object parameterValue : parameterValues) {
            if (parameterValue == null) {
                break;
            }
            parameterTypeNames.add(parameterValue.getClass().getName());
        }
        return parameterTypeNames;
    }

    /**
     * 匹配构造方法
     *
     * @param clazz           类型
     * @param parameterValues 实参
     * @return 构造方法
     */
    private Constructor<?> matchConstructor(Class<?> clazz, List<Object> parameterValues) {
        Constructor<?>[] constructors = clazz.getConstructors();

        int[] minLevels = new int[parameterValues.size()];
        Arrays.fill(minLevels, Integer.MAX_VALUE);

        Map<Constructor<?>, int[]> constructorLevels = new HashMap<>();
        for (Constructor<?> constructor : constructors) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            if (parameterTypes.length != parameterValues.size()) {
                continue;
            }
            int[] levels = calcIntimacy(parameterValues, parameterTypes, minLevels);
            constructorLevels.put(constructor, levels);
        }
        for (int i = 0; i < minLevels.length; i++) {
            int minLevel = minLevels[i];
            if (minLevel == Integer.MAX_VALUE) {
                throw new RuntimeException("实参参数(" + parameterValues.get(i) + ")不能和任何形参匹配");
            }
        }

        return findPriorityMethod(constructorLevels, minLevels);
    }

    /**
     * 计算每个实参与形参的亲近度，或者说参数直接转换的容易程度
     * 使用level来标识，0代表类型完全一致，1代表从实参类型往父类找一层就是形参类型
     *
     * @param parameterValues 实参集合
     * @param parameterTypes  形参集合
     * @param minLevels       当前已计算的最小层数
     * @return
     */
    private int[] calcIntimacy(List<Object> parameterValues, Class<?>[] parameterTypes, int[] minLevels) {
        int[] intimacyArr = new int[parameterTypes.length];
        Arrays.fill(intimacyArr, Integer.MAX_VALUE);
        for (int i = 0; i < parameterValues.size(); i++) {
            Class<?> parameterType = parameterTypes[i];
            Object parameterValue = parameterValues.get(i);

            int intimacy;
            if (parameterValue == null) {
                intimacy = parameterType.isPrimitive() ? Integer.MAX_VALUE : 0;
            } else {
                intimacy = classToSuperLevels(parameterValue.getClass(), parameterType);
            }

            minLevels[i] = Math.min(minLevels[i], intimacy);
            intimacyArr[i] = intimacy;
        }
        return intimacyArr;
    }

    /**
     * 从所有的方法层数中寻找最匹配的方法
     *
     * @param methodLevels 方法-层数集合
     * @param minLevels    最接近的参数层数
     * @param <T>          方法类型，可能是 Method 或者 Constructor
     * @return 匹配到的方法对象
     */
    private <T> T findPriorityMethod(Map<T, int[]> methodLevels, int[] minLevels) {
        int maxPriority = -1;
        T method = null;
        for (Map.Entry<T, int[]> entry : methodLevels.entrySet()) {
            int priority = 0;
            int[] levels = entry.getValue();
            for (int i = 0; i < minLevels.length; i++) {
                if (levels[i] != minLevels[i]) {
                    break;
                }
                priority++;
            }
            if (priority > maxPriority) {
                method = entry.getKey();
                maxPriority = priority;
            }
        }
        System.out.println("匹配到方法:" + formatMethod(method));
        return method;
    }

    private String formatMethod(Object method) {
        if (method == null) {
            return "null";
        }
        String name;
        Class<?>[] parameterTypes;
        if (method instanceof Method) {
            parameterTypes = ((Method) method).getParameterTypes();
            name = ((Method) method).getName();
        } else if (method instanceof Constructor<?>) {
            parameterTypes = ((Constructor<?>) method).getParameterTypes();
            name = ((Constructor<?>) method).getName();
        } else {
            return "null";
        }
        List<String> parameters = new ArrayList<>();
        for (Class<?> clazz : parameterTypes) {
            parameters.add(clazz.getName());
        }
        return String.format("%s(%s)", name, join(parameters, ","));
    }

    /**
     * 计算从一个类型往父类查找到另外一个类型所经历的次数
     * 如果找到顶层类型还是没有找到，则返回Integer.MAX_VALUE
     *
     * @param clazz       子类型
     * @param targetClazz 尝试查找的父类型
     * @return 当前类型到指定类型的距离，0代表两个类型一样，1代表目标类型是当前类型的父类，一次类推
     * 如果找不到目标类型则返回Integer.MAX_VALUE
     */
    private int classToSuperLevels(Class<?> clazz, Class<?> targetClazz) {
        if (clazz.equals(targetClazz)) {
            return 0;
        }
        int minLevel = Integer.MAX_VALUE;
        Stack<Class<?>> classStack = new Stack<>();
        classStack.push(clazz);
        while (!classStack.isEmpty()) {
            Class<?> parent = classStack.pop();
            System.out.println("尝试匹配类:" + parent.getName());
            int level = 0;
            boolean foundTarget = false;
            while (parent != Object.class) {
                if (parent.getSuperclass() == null || parent.getInterfaces() == null) {
                    break;
                }
                level++;
                if (targetClazz.equals(parent.getSuperclass())) {
                    foundTarget = true;
                    break;
                }
                classStack.push(parent.getSuperclass());
                for (Class<?> interfaceClass : parent.getInterfaces()) {
                    if (interfaceClass == targetClazz) {
                        foundTarget = true;
                        break;
                    }
                    classStack.push(interfaceClass);
                }
                if (foundTarget) {
                    break;
                }
                parent = parent.getSuperclass();
            }
            if (!foundTarget) {
                level = Integer.MAX_VALUE;
            }
            minLevel = Math.min(minLevel, level);
        }
        return minLevel;
    }

    public Object castValue(Object object, Class<?> valueType) {
        if (object == null) {
            return null;
        }
        String className = valueType.getName();
        switch (className) {
            case "byte":
            case "java.lang.Byte":
            case "short":
            case "java.lang.Short":
            case "int":
            case "java.lang.Integer":
            case "long":
            case "java.lang.Long":
                return TypeCastUtils.castToLong(object);
            case "float":
            case "java.lang.Float":
            case "java.lang.Double":
            case "double":
                return TypeCastUtils.castToDouble(object);
            case "java.math.BigDecimal":
                return TypeCastUtils.castToBigDecimal(object);
            case "java.lang.Boolean":
            case "boolean":
                return TypeCastUtils.castToBoolean(object);
            case "java.util.Date":
                return TypeCastUtils.castToDate(object);
            case "char":
            case "java.lang.Character":
            case "java.lang.String":
                return TypeCastUtils.castToString(object);
            default:
                break;
        }
        return object;
    }

    private static String join(Iterable<?> iterable, String separator) {
        return iterable == null ? null : join(iterable.iterator(), separator);
    }

    private static String join(Iterator<?> iterator, String separator) {
        if (iterator == null) {
            return null;
        } else if (!iterator.hasNext()) {
            return "";
        } else {
            Object first = iterator.next();
            if (!iterator.hasNext()) {
                return Objects.toString(first, "");
            } else {
                StringBuilder buf = new StringBuilder(256);
                if (first != null) {
                    buf.append(first);
                }

                while (iterator.hasNext()) {
                    if (separator != null) {
                        buf.append(separator);
                    }

                    Object obj = iterator.next();
                    if (obj != null) {
                        buf.append(obj);
                    }
                }

                return buf.toString();
            }
        }
    }
}
